using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using PulseLibrary;

namespace Pulse
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private Thread backgroundThread;

        enum ScreenState { TitleScreen, PlayTime, Loading, Paused, GameStatus, Credit }
        ScreenState currentScreen;

        private SpriteFont debugFont;
        private Texture2D titleScreenTexture;
        private Texture2D loadingScreenTexture;
        private Texture2D pauseScreenTexture;
        private Texture2D pixelTexture;
        private Vector2 screenSize;

        private bool isLoading;
        private bool debugMode;

        private KeyboardState prevKeyboardState = Keyboard.GetState();
        private KeyboardState currentKeyboardState = Keyboard.GetState();
        private MouseState currentMouseState = Mouse.GetState();
        private MouseState prevMouseState = Mouse.GetState();

        /* Game entities */
        Player player;
        Camera cam;
        Map map;
        ParticleEngine screenFireParticles;

        public GraphicsDeviceManager Graphics
        {
            get { return this.graphics; }
            set { this.graphics = value; }
        }

        public Game()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            //Components.Add(new InputManager(this));
            //graphics.ToggleFullScreen();
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            screenSize = new Vector2(graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height);

            this.IsMouseVisible = true;

            map = new Map(12, 30, screenSize);
            cam = new Camera();
            // 600, 600 will place the player on the raised tiles
            player = new Player(new Vector2(100, 600)); 

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            debugFont = Content.Load<SpriteFont>("Visual/ratefont");

            titleScreenTexture = Content.Load<Texture2D>("Visual/Menu/blue11");
            loadingScreenTexture = Content.Load<Texture2D>("Visual/Menu/victory");
            pauseScreenTexture = Content.Load<Texture2D>("Visual/Menu/TitleScreen");
            pixelTexture = Content.Load<Texture2D>("Visual/pixel");

            List<Texture2D> textures = new List<Texture2D>();
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/circle"));
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/circle1"));
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/star"));
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/diamond"));
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/blue"));
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/green"));
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/red"));
            //textures.Add(Content.Load<Texture2D>("Visual/Particles/Text-Particle2"));
            textures.Add(Content.Load<Texture2D>("Visual/Particles/particle3"));
            screenFireParticles = new ParticleEngine(textures, new Vector2(400, 240), false, Type.Smoke, 30, 20, 150);
            //fireParticles = new ParticleEngine(textures, new Vector2(400, 240), false);
            screenFireParticles.setDefaultColor(Color.OrangeRed);

            screenFireParticles.particleTarget = new RenderTarget2D(GraphicsDevice,
                GraphicsDevice.PresentationParameters.BackBufferWidth,
                GraphicsDevice.PresentationParameters.BackBufferHeight,
                true,
                GraphicsDevice.DisplayMode.Format,
                DepthFormat.Depth24);
            // TODO: use this.Content to load your game content here
        }

        protected void LoadGameGraphicContent()
        {
            LoadPlayerGraphics();
            LoadWorldGraphics();
            currentScreen = ScreenState.PlayTime;
        }

        protected void LoadPlayerGraphics()
        {
            String tempString;

            /* Loading player content */
            Texture2D playerTexture = Content.Load<Texture2D>("Visual/Player/player_walk");

            player.PlayerGraphics(playerTexture, graphics);

            for (int i = 0; i < 8; i++)
            {
                tempString = "walk" + i;
                player.playerSprite.AddAnimation(tempString, 0, (i * 96), 96, 96, 8, playerTexture.Width, 0.07f);

                tempString = "pause" + i;
                player.playerSprite.AddAnimation(tempString, (7 * 96), (i * 96), 96, 96, 1, playerTexture.Width, 0.1f);

            }
            player.playerSprite.CurrentAnimation = "pause1";
        }

        protected void LoadWorldGraphics()
        {
            Tile.tileSet = Content.Load<Texture2D>("Visual/World/part4_tileset_2");
            map.LoadMouseMap(Content.Load<Texture2D>("Visual/World/mousemap_2"), Content.Load<Texture2D>("Visual/World/hilight_2"));
            screenFireParticles.setDefaultColor(Color.OrangeRed);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        private void UpdateTitleScreen()
        {
            screenFireParticles.EmitterLocation = new Vector2(screenSize.X * 0.55f, screenSize.Y * 0.43f);
            screenFireParticles.Update(true);

            if (currentKeyboardState.IsKeyDown(Keys.Enter))
            {
                screenFireParticles.DeleteAllParticles();
                currentScreen = ScreenState.Loading;
                return;
            }
        }

        private void UpdatePlayTimeScreen(GameTime gametime)
        {
            if (currentKeyboardState.IsKeyDown(Keys.Enter))
            {
                currentScreen = ScreenState.Paused;
                return;
            }

            if (currentKeyboardState.IsKeyDown(Keys.Space) && prevKeyboardState.IsKeyUp(Keys.Space))
            {
                debugMode = !debugMode;
            }

            /* Update the map here */
            Vector2 hilightLoc = map.cursorPosition;
            Point hilightPoint = map.WorldToMapCell(new Point((int)hilightLoc.X, (int)hilightLoc.Y));

            if (currentMouseState.LeftButton == ButtonState.Pressed && prevMouseState.LeftButton != ButtonState.Pressed)
            {
                map.GetCell(hilightLoc).AddHeightTile(54);
            }
            else if (currentMouseState.RightButton == ButtonState.Pressed && prevMouseState.RightButton != ButtonState.Pressed)
            {
                map.GetCell(hilightLoc).RemoveTile();
            }
            map.Update(gametime, currentMouseState, cam.camPosition);

            /* Update the player here*/

            player.Update(gametime, currentKeyboardState);
            Point StandingOn = map.playerMapCell;
            Point FutureOn = map.WorldToMapCell(player.Position + player.velocity);

            /* If there is a tile higher or lower than the player */
            if (map.GetTileHeight(FutureOn) != map.GetTileHeight(StandingOn))
            {
                player.Position = player.playerSprite.PreviousPosition;
                player.velocity = Vector2.Zero;
            }

            /* If the tile is untraversable, such as water */
            if (map.GetCell(FutureOn).Walkable == false)
            {
                player.Position = player.playerSprite.PreviousPosition;
                player.velocity = Vector2.Zero;
            }

            //Change the sprite's position, where it is actually drawn
            player.playerSprite.BasicMove(player.velocity.X, player.velocity.Y);

            player.hitbox.Y = (int)(player.Position.Y + player.drawOffset.Y);
            player.hitbox.X = (int)(player.Position.X + player.drawOffset.X); 

            // Offset the player visually for being on blocks above ground
            player.playerHeight = map.GetTileHeight(StandingOn) * Tile.HeightTileOffset;
            player.hitbox.Y -= player.playerHeight;
            player.playerSprite.drawOrder = map.playerDrawDepth;
            
            /* Update the particles here */
            Vector2 heightOffset = new Vector2(0, -player.playerHeight);
            //fireParticles.EmitterLocation = player.Position + heightOffset;
            //fireParticles.Update(currentKeyboardState.IsKeyDown(Keys.LeftShift));
            screenFireParticles.EmitterLocation = new Vector2(currentMouseState.X, currentMouseState.Y);
            screenFireParticles.Update(currentMouseState.LeftButton == ButtonState.Pressed);

            //for (int i = 0; i < fireParticles.particless.Count(); i++)
            //{
            //    Point currentCell = map.WorldToMapCell(fireParticles.particless[i].Position - heightOffset);
            //    fireParticles.UpdateDrawOrder(i, map.GetTopDrawOrder(currentCell.X, currentCell.Y));
            //}

            /* Update the camera here */
            Vector2 pHeight = new Vector2(0, player.playerHeight);
            cam.Update(new Vector3(player.Position + player.spriteCentre 
                + player.drawOffset - pHeight, 0), 
                gametime, currentKeyboardState);
        }

        private void UpdatePauseScreen()
        {
            if (currentKeyboardState.IsKeyDown(Keys.B))
            {
                currentScreen = ScreenState.PlayTime;
            }
        }

        private void UpdateStatusScreen()
        {

        }

        private void UpdateCredit()
        {

        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gametime)
        {
            currentKeyboardState = Keyboard.GetState();
            currentMouseState = Mouse.GetState();

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            // TODO: Add your update logic here

            switch (currentScreen)
            {
                case ScreenState.TitleScreen:
                    {
                        UpdateTitleScreen();
                        break;
                    }
                case ScreenState.PlayTime:
                    {
                        if (isLoading)
                        {
                            LoadGameGraphicContent();
                            isLoading = false;
                            break;
                        }
                        else
                        {
                            UpdatePlayTimeScreen(gametime);
                            break;
                        }
                    }
                case ScreenState.Loading:
                    {
                        if (!isLoading)
                        {
                            backgroundThread = new Thread(LoadGameGraphicContent);
                            isLoading = true;
                            backgroundThread.Start();
                        }
                        break;
                    }
                case ScreenState.Paused:
                    {
                        UpdatePauseScreen();
                        break;
                    }
                case ScreenState.GameStatus:
                    {
                        UpdateStatusScreen();
                        break;
                    }
                case ScreenState.Credit:
                    {
                        UpdateCredit();
                        break;
                    }
            }

            prevKeyboardState = currentKeyboardState;
            prevMouseState = currentMouseState;
            base.Update(gametime);
        }

        private void DrawTitleScreen()
        {
            GraphicsDevice.Clear(Color.Black);

            Rectangle rectangleScreenSize = new Rectangle(0, 0, (int)screenSize.X, (int)screenSize.Y);

            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);
            spriteBatch.Draw(titleScreenTexture, rectangleScreenSize, Color.White);
            screenFireParticles.Draw(spriteBatch);
            spriteBatch.DrawString(debugFont, "Press Enter", Vector2.Zero, 
                Color.AliceBlue, 0.0f, Vector2.Zero, 4.0f, SpriteEffects.None, 0.0f);
            spriteBatch.End();
        }

        private void DrawPlayTimeScreen()
        {
            /* Draw the particles to a seperate render target before drawing the main game */
            GraphicsDevice.SetRenderTarget(screenFireParticles.particleTarget);
            GraphicsDevice.Clear(Color.Transparent);

            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.Additive, 
                SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone, null);

            screenFireParticles.Draw(spriteBatch);

            spriteBatch.End();

            GraphicsDevice.SetRenderTarget(null);
            Texture2D particleMap = (Texture2D)screenFireParticles.particleTarget;

            /* Draw the elements of the main game here */
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null, cam.getTransformation(graphics));

            map.Draw(spriteBatch, player.Position);
            player.Draw(spriteBatch, 0, -player.playerHeight);

            if (debugMode)
                DrawDebugScreen();

            spriteBatch.End();

            /* 
             * Draw the screen particles here (they're called screen particles 
             * because they aren't part of the game, just overlayed on top of it) 
             */
            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend,
                SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone, null);

            spriteBatch.Draw(particleMap, Vector2.Zero, null, Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0);

            spriteBatch.End();
        }

        private void DrawDebugScreen()
        {
            spriteBatch.Draw(pixelTexture, player.hitbox, null, Color.Red * 0.5f, 0.0f,
                Vector2.Zero, SpriteEffects.None, 0.0f);
            
            // The place to draw the debug font in relation to the player
            Vector2 pHeight = screenSize / 2;
            pHeight.Y += player.playerHeight;

            String tempString = "Rotation: " + MathHelper.ToDegrees(player.Rotation).ToString()
                + "\nWorld Player Location: " + player.Position
                + "\nWorld Mouse Location: " + map.cursorPosition
                + "\nPlayer draw order: " + map.playerDrawDepth
                + "\nParticless count: " + (screenFireParticles.particles.Count()
                + "\nFreePartis count: " + (screenFireParticles.freeParticlesCount))
                + "\nCurrentFrame: " + player.playerSprite.CurrentFrameAnimation.currentFrame;
            spriteBatch.DrawString(debugFont, tempString, player.Position - pHeight, Color.White);

            /* The code to draw the tile coordinates on top of each tile */
            Vector2 firstSquare = Vector2.Zero;
            int firstX = (int)firstSquare.X;
            int firstY = (int)firstSquare.Y;

            for (int y = 0; y < map.mapHeight; y++)
            {
                int rowOffset = 0;
                if ((firstY + y) % 2 == 1)
                    rowOffset = Tile.OddRowXOffset;

                /* 
                    * magic numbers 52 and 90 are used 
                    * to offset the coordinate text to appear 
                    * directly in the middle of the base tile
                    */
                for (int x = 0; x < map.mapWidth; x++)
                {
                    spriteBatch.DrawString(debugFont, x + "," + y,
                        new Vector2((x * Tile.TileStepX) + rowOffset,
                            (y * Tile.TileStepY) + Tile.HeightTileOffset),
                            Color.White, 0f, Vector2.Zero, 1.0f,
                            SpriteEffects.None, 0.0f);
                }
            }

            /* The code for drawing the tile hilight that follows the mouse */
            Vector2 hilightLoc = map.cursorPosition;
            Point hilightPoint = map.WorldToMapCell(new Point((int)hilightLoc.X, (int)hilightLoc.Y));
            Cell currentCell = map.GetCell(hilightPoint.X, hilightPoint.Y);

            int hilightrowOffset = 0;
            if ((hilightPoint.Y) % 2 == 1)
                hilightrowOffset = Tile.OddRowXOffset;

            Color highlightColor;
            if (currentCell.heightTiles.Count > 0)
            {
                highlightColor = Color.Blue * 0.3f;
            }
            else
            {
                highlightColor = Color.Red * 0.3f;
            }

            spriteBatch.Draw(
                            map.highlight,
                                new Vector2(
                                    (hilightPoint.X * Tile.TileStepX) + hilightrowOffset,
                                    (hilightPoint.Y + 2) * Tile.TileStepY),

                            new Rectangle(0, 0, 128, 64),
                            highlightColor,
                            0.0f,
                            Vector2.Zero,
                            1.0f,
                            SpriteEffects.None,
                            0.0f);
        }

        private void DrawLoadingScreen()
        {
            spriteBatch.Begin();
            spriteBatch.Draw(loadingScreenTexture, Vector2.Zero, Color.White);
            spriteBatch.End();
        }

        private void DrawPauseScreen()
        {
            spriteBatch.Begin();
            Rectangle screen = new Rectangle(0, 0, (int)screenSize.X, (int)screenSize.Y);
            spriteBatch.Draw(pauseScreenTexture, screen, Color.Red * 0.8f);
            spriteBatch.End();
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            switch (currentScreen)
            {
                case ScreenState.TitleScreen:
                    {
                        DrawTitleScreen();
                        break;
                    }
                case ScreenState.PlayTime:
                    {
                        DrawPlayTimeScreen();
                        break;
                    }
                case ScreenState.Loading:
                    {
                        DrawLoadingScreen();
                        break;
                    }
                case ScreenState.Paused:
                    {
                        DrawPlayTimeScreen();
                        DrawPauseScreen();
                        break;
                    }
                case ScreenState.GameStatus:
                    {
                        break;
                    }
                case ScreenState.Credit:
                    {
                        break;
                    }
            }

            base.Draw(gameTime);
        }
    }
}
